文本效果

TextView 效果

文本折叠

/**
 *
 * 注意:支持富文本
 *
 * 超过指定行数后的字符串显示"...See More"或者"...See Less"
 * 用法:1.XML:<MoreLessRichTextView></>
 *      2.调用MoreLessRichTextView实例对象的showText(text)
 *      说明:如果仅仅在xml中或对象创建了一个MoreLessRichTextView,相当于AppCompatTextView使用,必须调用showText(text)函数see more 和 see less才生效
 */
@Suppress("DEPRECATION")
@SuppressLint("WrongConstant")
class MoreLessRichTextView @JvmOverloads constructor(
    val mContext: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = android.R.attr.textViewStyle
) : AppCompatTextView(mContext, attrs, defStyleAttr) {

    // 显示的行数,超过即隐藏,see more替代
    private var maxShowLine = -1
    private var actualLine = -1
    private var tripleDot: String = "..."
    private var seeMoreText: String = "See More"
    private var seeLessText: String = "See Less"
    private var seeMoreTextSize by Delegates.notNull<Float>()
    private var seeLessTextSize by Delegates.notNull<Float>()
    private var seeMoreTextColor: Int? = null
    private var seeLessTextColor: Int? = null
    private var shouldAutoExpand: Boolean = true

    private var originContentText: CharSequence? = null
    private var showState: ShowState = ShowState.EXPAND

    private var seeMoreEnable = true
    private var seeLessEnable = true

    private var moreSpanClickEnable = true
    private var lessSpanClickEnable = true

    private var setTripleDotShowEnable: ((maxShowLine: Int) -> Boolean) = { true }

    private var needFold = false

    private var onSpanClickListener: ((state: ShowState) -> Unit)? = null
    private var onTextClickListener: OnClickListener? = null
    private val showingText = SpannableStringBuilder()

    init {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            breakStrategy = Layout.BREAK_STRATEGY_SIMPLE
        }
        highlightColor = Color.TRANSPARENT
    }

    override fun onFinishInflate() {
        super.onFinishInflate()
        seeMoreTextColor = currentTextColor
        seeLessTextColor = currentTextColor

        seeMoreTextSize = textSize
        seeLessTextSize = textSize
    }

    private var firstDraw = true

    private fun getOriginContentText(): CharSequence {
        return originContentText ?: ""
    }

    private fun getTripleDotTxt(): String {
        return if (setTripleDotShowEnable.invoke(maxShowLine)) {
            tripleDot
        } else {
            ""
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        if (!seeMoreEnable && !seeLessEnable) {
            return
        }
        try {
            if (firstDraw && maxShowLine >= 0) {
                val realLines = if (actualLine >= 0) {
                    actualLine
                } else {
                    lineCount
                }
                if (maxShowLine >= realLines) {
                    needFold = false
                    return
                }
                needFold = true
                var start = 0
                var end: Int
                showingText.clear()
                for (i in 0 until maxShowLine) {
                    end = layout.getLineEnd(i)
                    if (end < 0) {
                        end = start
                    }
                    if (end > getOriginContentText().length) {
                        end = getOriginContentText().length
                    }
                    if (i == maxShowLine - 1) {
                        val subSequence = getOriginContentText().subSequence(start, end)
                        val optSeeMoreLineSequence = optSeeMoreLine(subSequence)
                        showingText.append(optSeeMoreLineSequence)
//                        if (optSeeMoreLineSequence is Spanned) {
//                            // Copy existing spans to the new SpannableString
//                            val spans =
//                                optSeeMoreLineSequence.getSpans(
//                                    0,
//                                    subSequence.length,
//                                    Any::class.java
//                                )
//                            for (span in spans) {
//                                val start1 = optSeeMoreLineSequence.getSpanStart(span)
//                                val end1 = optSeeMoreLineSequence.getSpanEnd(span)
//                                val flags = optSeeMoreLineSequence.getSpanFlags(span)
//                                showingText.setSpan(span, start + start1, start + end1, flags)
//                            }
//                        }
                    } else {
                        val subSequence = getOriginContentText().subSequence(start, end)
                        showingText.append(subSequence)
                    }
                    start = end
                }
                // 处理下富文本跨行显示异常,将原文本的富文本同步到showingText
                val originalText = getOriginContentText()
                if (originalText.isNotEmpty() && originalText is Spanned) {
                    // Copy existing spans to the new SpannableString
                    val spans = originalText.getSpans(0, originalText.length, Any::class.java)
                    for (span in spans) {
                        val s = originalText.getSpanStart(span)
                        var e = originalText.getSpanEnd(span)
                        val flags = originalText.getSpanFlags(span)
                        if (s >= showingText.length) {
                            continue
                        }
                        if (e > showingText.length) {
                            e = showingText.length
                        }
                        showingText.setSpan(span, s, e, flags)
                    }
                }
                if (showingText.endsWith("\n")) {
                    try {
                        showingText.delete(showingText.length - 1, showingText.length)
                    } catch (e: Exception) {
                        //不需要处理,继续使用原字符串即可
                    }
                }
                showingText.append("${getTripleDotTxt()} $seeMoreText")
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (!seeMoreEnable && !seeLessEnable) {
            return
        }
        try {
            if (firstDraw) {
                firstDraw = false
                if (needFold && showingText.isNotEmpty()) {
                    setSeeMoreSpan(showingText)
                } else {
                    text = getOriginContentText()
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }

    }

    //处理最后一行文字的
    private fun optSeeMoreLine(rawContent: CharSequence): CharSequence {
        //总宽度
        val rawWidth = this.measuredWidth
        //点点点的宽度
        val dotWidth = this.paint.measureText("${getTripleDotTxt()} ")
        val rawTextSize = this.paint.textSize
        this.paint.textSize = seeMoreTextSize
        //see more的宽度
        val seeMoreWidth = this.paint.measureText(seeMoreText)
        this.paint.textSize = rawTextSize
        val rawContextWidth = getTextWidthOrWithReplaceSpanSize(rawContent)
        val leftWidth =
            rawWidth - rawContextWidth - dotWidth - seeMoreWidth
//        Log.d(
//            "hacket",
//            "rawWidth=$rawWidth, rawContextWidth: $rawContextWidth, leftWidth: $leftWidth, rawContent=$rawContent"
//        )
        if (leftWidth >= 0) {
            return rawContent
        }

        var tempContent: CharSequence
        val expectWidth = rawWidth - dotWidth - seeMoreWidth
        for (i in rawContent.indices) {
            tempContent = rawContent.subSequence(0, rawContent.length - i)
            val tempContentSize = getTextWidthOrWithReplaceSpanSize(tempContent)
            if (tempContentSize <= expectWidth) {
//                Log.i(
//                    "hacket",
//                    "paintWidth: $paintWidth, tempContentSize=$tempContentSize, expectWidth: $expectWidth, tempContent=$tempContent"
//                )
                return tempContent
            }
        }
        return rawContent
    }

    /**
     * 获取文本宽度,如果文本中包含ReplacementSpan,则需要遍历计算
     *
     * @param text 文本 CharSequence
     */
    private fun getTextWidthOrWithReplaceSpanSize(text: CharSequence?): Float {
        if (text == null) {
            return 0f
        }

        val textPaint = paint
        var totalWidth = 0f
        if (text !is Spanned) { // 普通文本
            return textPaint.measureText(text.toString())
        }

        // 获取所有ReplacementSpan

        val spans = text.getSpans(0, text.length, ReplacementSpan::class.java)
        var lastSpanEnd = 0
        // 遍历所有ReplacementSpan以及普通文本
        spans.forEach { span ->
            val start = text.getSpanStart(span)
            val end = text.getSpanEnd(span)

            // 将span前的普通文本宽度添加至totalWidth
            if (start > lastSpanEnd) {
                val normalTextWidth = textPaint.measureText(text, lastSpanEnd, start)
                totalWidth += normalTextWidth
            }

            // 将ReplacementSpan的宽度添加至totalWidth
            val spanWidth = span.getSize(textPaint, text, start, end, null)
            totalWidth += spanWidth
            lastSpanEnd = end
        }
        // 将最后一个ReplacementSpan后的普通文本宽度添加至totalWidth
        if (lastSpanEnd < text.length) {
            val remainingTextWidth = textPaint.measureText(text, lastSpanEnd, text.length)
            totalWidth += remainingTextWidth
        }
        return totalWidth
    }

    @SuppressLint("ResourceType")
    private fun setSeeMoreSpan(newText: CharSequence) {
        val spannableString = SpannableString(newText)

        // 处理spanned的url点击事件
        if (newText is Spanned) {
            // Copy existing spans to the new SpannableString
            val spans = newText.getSpans(0, newText.length, Any::class.java)
            for (span in spans) {
                val start = newText.getSpanStart(span)
                val end = newText.getSpanEnd(span)
                val flags = newText.getSpanFlags(span)
                spannableString.setSpan(span, start, end, flags)
            }
        }

        val start = newText.length - seeMoreText.length
        val end = newText.length
        val flag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE

        //设置颜色
        val colorSpan = ForegroundColorSpan(seeMoreTextColor!!)
        spannableString.setSpan(colorSpan, start, end, flag)

        //设置字体大小
        val sizeSpan = AbsoluteSizeSpan(floor(seeMoreTextSize).toInt())
        spannableString.setSpan(sizeSpan, start, end, flag)

        if (moreSpanClickEnable) {
            spannableString.setSpan(object : ClickableSpan() {
                override fun updateDrawState(ds: TextPaint) {
                    ds.isUnderlineText = false
                }

                override fun onClick(widget: View) {
                    onTextClickListener?.onClick(widget)
                }
            }, 0, start - 1, flag)
            //设置 More Text 点击
            spannableString.setSpan(object : ClickableSpan() {
                override fun updateDrawState(ds: TextPaint) {
                    ds.isUnderlineText = false
                }

                override fun onClick(widget: View) {
                    onSpanClickListener?.invoke(showState)
                    if (shouldAutoExpand) {
                        maxLines = Int.MAX_VALUE
                        text = getOriginContentText()
                        showState = ShowState.EXPAND
                        seeLess()
                    }
                }
            }, start, end - 1, flag)
            movementMethod = LinkMovementMethod.getInstance()
        }

        setText(spannableString, BufferType.SPANNABLE)
    }

    fun setTextClickListener(onClickListener: OnClickListener) {
        onTextClickListener = onClickListener
    }

    @SuppressLint("ResourceType")
    private fun seeLess() {
        if (seeLessEnable) {
            val start = getOriginContentText().length + 1
            val end = getOriginContentText().length + seeLessText.length + 1
            val flag = Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
            val text = SpannableStringBuilder(getOriginContentText()).append(" $seeLessText")
            val spannableString = SpannableString(text)

            //设置字体大小
            val sizeSpan = AbsoluteSizeSpan(floor(seeLessTextSize).toInt())
            spannableString.setSpan(sizeSpan, start, end, flag)

            if (lessSpanClickEnable) {
                //设置点击
                spannableString.setSpan(object : ClickableSpan() {
                    override fun updateDrawState(ds: TextPaint) {
                        ds.isUnderlineText = false
                    }

                    override fun onClick(widget: View) {
                        maxLines = maxShowLine
                        firstDraw = true
                        setText(getOriginContentText())
                        showState = ShowState.SHRINK
                        onSpanClickListener?.invoke(showState)
                    }
                }, start, end - 1, flag)
                //设置字体颜色
                val colorSpan = ForegroundColorSpan(seeLessTextColor!!)
                spannableString.setSpan(colorSpan, start, end, flag)
                movementMethod = LinkMovementMethod.getInstance()
            }
            setText(spannableString, BufferType.SPANNABLE)
        }
    }

    /**
     * 当调用了show函数才生效,不调用和TextView相同
     */
    fun showText(text: CharSequence, urlClickBlock: ((url: String) -> Unit)? = null) {
        this.originContentText = text

        // 处理spanned的url点击事件
        if (text is Spanned) {

            // Create a new SpannableString from the Spanned object
            val spannableString = SpannableStringBuilder(text)

            // Copy existing spans to the new SpannableString
            val spans = text.getSpans(0, text.length, Any::class.java)
            for (span in spans) {
                val start = text.getSpanStart(span)
                val end = text.getSpanEnd(span)
                val flags = text.getSpanFlags(span)
                spannableString.setSpan(span, start, end, flags)
            }

            // Find any existing URLSpan objects
            val urlSpans = spannableString.getSpans(0, spannableString.length, URLSpan::class.java)
            if (urlSpans.isNotEmpty() && urlClickBlock != null) {
                movementMethod = LinkMovementMethod.getInstance()
            }
            // Replace each URLSpan with our custom ClickableSpan
            urlSpans.forEach { oldSpan ->
                val start = spannableString.getSpanStart(oldSpan)
                val end = spannableString.getSpanEnd(oldSpan)

                // Remove the existing URLSpan
                spannableString.removeSpan(oldSpan)

                // Create a new custom ClickableSpan
                val clickable = object : ClickableSpan() {
                    override fun onClick(view: View) {
                        // Pass the URL to the provided onUrlClick function
                        urlClickBlock?.invoke(oldSpan.url)
                    }

                    override fun updateDrawState(textPaint: TextPaint) {
                        super.updateDrawState(textPaint)
                        // Apply the custom link color and remove the underline
                        textPaint.color =
                            ContextCompat.getColor(context, android.R.color.holo_blue_dark)
                        textPaint.isUnderlineText = false
                    }
                }

                // Apply the new ClickableSpan to the SpannableString
                spannableString.setSpan(clickable, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
            }
            this.originContentText = spannableString
        }
        maxLines = maxShowLine
        firstDraw = true
        needFold = false
        setText(getOriginContentText())
    }

    fun setAutoExpandSeeMore(auto: Boolean): MoreLessRichTextView {
        this.shouldAutoExpand = auto
        return this
    }

    /**
     * 设置是否显示see more
     */
    fun setSeeMoreEnable(enable: Boolean): MoreLessRichTextView {
        this.seeMoreEnable = enable
        return this
    }

    /**
     * 设置是否显示see less
     */
    fun setSeeLessEnable(enable: Boolean): MoreLessRichTextView {
        this.seeLessEnable = enable
        return this
    }

    /**
     * 设置see more字体颜色
     */
    fun setSeeMoreTextColor(@ColorRes color: Int): MoreLessRichTextView {
        this.seeMoreTextColor = resources.getColor(color)
        return this
    }

    /**
     * 设置see more字体颜色
     */
    fun setSeeLessTextColor(@ColorRes color: Int): MoreLessRichTextView {
        this.seeLessTextColor = resources.getColor(color)
        return this
    }

    /**
     * 设置see More字号(SP)
     */
    @JvmName("setSeeMoreTextSize1")
    fun setSeeMoreTextSize(sp: Float): MoreLessRichTextView {
        this.seeMoreTextSize = sp.dp
        return this
    }

    /**
     * 设置see More字号(SP)
     */
    @JvmName("setSeeLessTextSize1")
    fun setSeeLessTextSize(sp: Float): MoreLessRichTextView {
        this.seeLessTextSize = sp.dp
        return this
    }

    /**
     * 设置see more提示语
     */
    fun setSeeMoreText(seeMoreText: String): MoreLessRichTextView {
        this.seeMoreText = seeMoreText
        return this
    }

    /**
     * 设置see more提示语
     */
    fun setSeeLessText(seeMoreText: String): MoreLessRichTextView {
        this.seeLessText = seeMoreText
        return this
    }

    /**
     * 设置最大显示行数,超过该行数折叠
     */
    fun setMaxShowLine(maxLine: Int): MoreLessRichTextView {
        this.maxShowLine = maxLine
        return this
    }

    /**
     * 设置当前显示状态:SHRINK(缩进) or EXPAND(展开), 默认SEE MORE
     */
    fun setShowState(showState: ShowState): MoreLessRichTextView {
        this.showState = showState
        return this
    }

    /**
     * 设置more span是否可点击
     */
    fun setMoreSpanClickEnable(enable: Boolean): MoreLessRichTextView {
        this.moreSpanClickEnable = enable
        return this
    }

    /**
     * 设置less span是否可点击
     */
    fun setLessSpanClickEnable(enable: Boolean): MoreLessRichTextView {
        this.lessSpanClickEnable = enable
        return this
    }

    fun setTripleDotShow(enable: (maxShowLine: Int) -> Boolean): MoreLessRichTextView {
        this.setTripleDotShowEnable = enable
        return this
    }

    /**
     * 获取当前显示状态:SHRINK(缩进) or EXPAND(展开), 默认SEE MORE
     */
    fun getShowState(): ShowState {
        return showState
    }

    @Keep
    enum class ShowState {
        SHRINK,
        EXPAND
    }
}

使用:

class TextViewFollowActivity : AppCompatActivity(), OnClickableSpanListener {  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        enableEdgeToEdge()  
        setContentView(R.layout.activity_text_view_follow)  
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->  
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())  
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)  
            insets  
        }  
  
        val tvNormal = findViewById<TextView>(R.id.tv_normal)  
        val span = getSpanText(tvNormal, this)  
        tvNormal.text = span  
  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less0).apply {  
            setMaxShowLine(2)  
            setSeeMoreEnable(true)  
            setSeeLessEnable(true)  
            setSeeMoreText("展示")  
            setSeeLessText("收起")  
            setSeeMoreTextColor(android.R.color.holo_red_dark)  
            setSeeLessTextColor(android.R.color.holo_blue_dark)  
  
            val text =  
                "1这是没有富文本的测试的,2这是没有富文本的测试的,3这是没有富文本的测试的,4这是没有富文本的测试的,5这是没有富文本的测试的,6这是没有富文本的测试的,7这是没有富文本的测试的,8这是没有富文本的测试的,9这是没有富文本的测试的,10这是没有富文本的测试的,11这是没有富文本的测试的。"  
            showText(text)  
        }  
  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less).apply {  
            setSeeMoreEnable(true)  
            setMaxShowLine(1)  
            setSeeMoreTextColor(android.R.color.holo_red_dark)  
            setSeeLessTextColor(android.R.color.holo_blue_dark)  
            setSeeMoreText("展示")  
            setSeeLessText("收起")  
  
            val text = getSpanText(this, this@TextViewFollowActivity)  
            showText(text)  
  
        }  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less1).apply {  
            setSeeMoreEnable(true)  
            setMaxShowLine(2)  
            setSeeMoreTextColor(android.R.color.holo_red_dark)  
            setSeeLessTextColor(android.R.color.holo_blue_dark)  
            setSeeMoreText("展示")  
            setSeeLessText("收起")  
  
            val text = getSpanText(this, this@TextViewFollowActivity)  
            text.append("这是2行的,这是2行的,这是2行的,这是2行的,这是2行的end")  
            showText(text)  
        }  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less2).apply {  
            setSeeMoreEnable(true)  
            setSeeMoreTextColor(android.R.color.holo_red_dark)  
            setMaxShowLine(3)  
            setSeeMoreTextColor(android.R.color.holo_red_dark)  
            setSeeLessTextColor(android.R.color.holo_blue_dark)  
            setSeeMoreText("展示")  
            setSeeLessText("收起")  
  
            val text = getSpanText(this, this@TextViewFollowActivity)  
            text.append("这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的end")  
            showText(text)  
        }  
  
        findViewById<MoreLessRichTextView>(R.id.tv_html).apply {  
            val longHtmlText =  
                "html这是3行的,<fon color=#FF00FF>这是3行的</font>,<font color=#A86104>哈哈哈哈</font>这是3行的,这是3行的,这是3行的,<strong>加重文本</strong> <em>强调文本</em>这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的,这是3行的end<strong>加重文本</strong> <em>强调文本</em> "  
            setSeeMoreEnable(true)  
            setSeeMoreTextColor(android.R.color.holo_red_dark)  
            setMaxShowLine(3)  
            setSeeMoreTextColor(android.R.color.holo_red_dark)  
            setSeeLessTextColor(android.R.color.holo_blue_dark)  
            setSeeMoreText("展示")  
            setSeeLessText("收起")  
            showText(Html.fromHtml(longHtmlText))  
        }  
  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less_info).apply {  
            setMaxShowLine(1)  
            setSeeMoreEnable(true)  
            setSeeLessEnable(false)  
            setMoreSpanClickEnable(false)  
            setLessSpanClickEnable(false)  
            setSeeMoreText(">")  
            setTripleDotShow { maxShowLine ->  
                maxShowLine >= 3  
            }  
  
            val text = getSpanText2(this, this@TextViewFollowActivity)  
            showText(text)  
        }  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less_info2).apply {  
            setMaxShowLine(2)  
            setSeeMoreEnable(true)  
            setSeeLessEnable(false)  
            setMoreSpanClickEnable(false)  
            setLessSpanClickEnable(false)  
            setSeeMoreText(">")  
            setTripleDotShow { maxShowLine ->  
                maxShowLine >= 3  
            }  
  
            val text = getSpanText2(this, this@TextViewFollowActivity)  
            showText(text)  
        }  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less_info3).apply {  
            setMaxShowLine(3)  
            setSeeMoreEnable(true)  
            setSeeLessEnable(false)  
            setMoreSpanClickEnable(false)  
            setLessSpanClickEnable(false)  
            setSeeMoreText(">")  
            setTripleDotShow { maxShowLine ->  
                maxShowLine >= 3  
            }  
  
            val text = getSpanText2(this, this@TextViewFollowActivity)  
            showText(text)  
        }  
        findViewById<MoreLessRichTextView>(R.id.tv_more_less_info4).apply {  
            setMaxShowLine(5)  
            setSeeMoreEnable(true)  
            setSeeLessEnable(false)  
            setMoreSpanClickEnable(false)  
            setLessSpanClickEnable(false)  
            setSeeMoreText(">")  
            setTripleDotShow { maxShowLine ->  
                maxShowLine >= 5  
            }  
  
            val text = getSpanText2(this, this@TextViewFollowActivity)  
            showText(text)  
        }  
    }  
  
    private fun getSpanText2(  
        textview: TextView,  
        listener: OnClickableSpanListener  
    ): SpannableStringBuilder {  
        val spanBuild = SimplifySpanBuild()  
        spanBuild  
            .append(  
                SpecialImageUnit(  
                    applicationContext,  
                    BitmapFactory.decodeResource(resources, R.drawable.live_avatar_default)  
                )  
                    .setGravity(SpecialGravity.CENTER)  
  
            )  
            .append("Hurry up!")  
            .append(  
                SpecialTextUnit("Cheaper than added").setClickableUnit(  
                    SpecialClickableUnit(  
                        textview,  
                        listener  
                    ).setTag("1").setPressBgColor(Color.BLUE)  
                ).setTextColor(  
                    Color.parseColor("#FA6338")  
                )  
            )  
            .append(  
                SpecialTextUnit("[括号内这是用来测试跨行的富文本显示的,颜色为红色]").setClickableUnit(  
                    SpecialClickableUnit(  
                        textview,  
                        listener  
                    ).setTag("1").setPressBgColor(Color.BLUE)  
                ).setTextColor(Color.RED)  
            )  
            .append(  
                SpecialTextUnit("[括号内这是用来测试最后一行富文本的显示,哈哈哈哈哈哈哈,颜色为蓝色]").setClickableUnit(  
                    SpecialClickableUnit(  
                        textview,  
                        listener  
                    ).setTag("1").setPressBgColor(Color.BLUE)  
                ).setTextColor(Color.BLUE)  
            )  
            .append(",up to \$10!up to \$1 up to \$10!up to \$10tup to o \$10up to \$10!up to \$1 up to \$10!up to \$10tup to o \$10up to \$10!up to \$1 up to \$10!up to \$10tup to o \$10up to \$10!up to \$1 up to \$10!up to \$10t up to o \$10!up to o \$10! up to o \$10! up to o \$10! up to o \$10! up to o \$10!")  
        return spanBuild.build()  
    }  
  
    private fun getSpanText(  
        textview: TextView,  
        listener: OnClickableSpanListener  
    ): SpannableStringBuilder {  
        val linkNorTextColor = -0xb7c275  
        val linkPressBgColor = -0x783106  
  
        val spanBuild = SimplifySpanBuild()  
        spanBuild.append("无默认背景11]")  
            .append(  
                SpecialImageUnit(  
                    applicationContext,  
                    BitmapFactory.decodeResource(resources, R.drawable.level)  
                )  
                    .setGravity(SpecialGravity.CENTER)  
  
            )  
            .append(  
                SpecialTextUnit("[点我点我000").setClickableUnit(  
                    SpecialClickableUnit(  
                        textview,  
                        listener  
                    ).setTag("1").setPressBgColor(-0xb000)  
                ).setTextColor(  
                    Color.BLUE  
                )  
            )  
            .appendMultiClickable(  
                SpecialClickableUnit(textview, listener).setNormalTextColor(linkNorTextColor)  
                    .setPressBgColor(linkPressBgColor),  
                " ",  
                SpecialImageUnit(  
                    applicationContext,  
                    BitmapFactory.decodeResource(resources, R.drawable.level),  
                    80,  
                    50  
                )  
                    .setGravity(SpecialGravity.CENTER),  
                SpecialTextUnit(" 用户名 ")  
            )  
            .append(  
                SpecialTextUnit("[点我点我1").setClickableUnit(  
                    SpecialClickableUnit(  
                        textview,  
                        listener  
                    ).setTag("1").setPressBgColor(-0xb000)  
                ).setTextColor(  
                    Color.BLUE  
                )  
            )  
            .append("哈哈哈")  
            .append(  
                SpecialTextUnit("[括号内测试富文本跨行显示,颜色红色]").setClickableUnit(  
                    SpecialClickableUnit(  
                        textview,  
                        listener  
                    ).setTag("1").setPressBgColor(-0xb000)  
                ).setTextColor(Color.RED)  
            )  
            .append("无默认背景显示下划线")  
            .append(  
                SpecialImageUnit(  
                    applicationContext,  
                    BitmapFactory.decodeResource(resources, R.drawable.level)  
                )  
                    .setGravity(SpecialGravity.CENTER)  
  
            )  
            .append(  
                SpecialTextUnit("点我点我2").setClickableUnit(  
                    SpecialClickableUnit(textview, listener).setTag("2").showUnderline()  
                        .setPressBgColor(-0xb000).setPressTextColor(  
                            Color.WHITE  
                        )  
                ).setTextColor(-0xb000)  
            )  
            .append("有默认背景")  
            .append(  
                SpecialImageUnit(  
                    applicationContext,  
                    BitmapFactory.decodeResource(resources, R.drawable.level),  
                    120,  
                    120  
                )  
                    .setGravity(SpecialGravity.CENTER)  
  
            )  
            .append(  
                SpecialTextUnit("点我点我3").setClickableUnit(  
                    SpecialClickableUnit(textview, listener).setTag("3").setPressBgColor(  
                        Color.BLUE  
                    ).setPressTextColor(Color.WHITE)  
                ).setTextColor(-0xb000).setTextBackgroundColor(-0x783115)  
            )  
            .append(  
                SpecialImageUnit(  
                    applicationContext,  
                    BitmapFactory.decodeResource(resources, R.drawable.level),  
                    180,  
                    180  
                )  
                    .setGravity(SpecialGravity.CENTER)  
  
            )  
            .append("我只是个结尾")  
        return spanBuild.build()  
    }  
  
    override fun onClick(tv: TextView?, clickableSpan: CustomClickableSpan?) {  
        Log.d("hacket", "onClick, tv=$tv, clickableSpan=$clickableSpan")  
        toast("onClick, tv=$tv, clickableSpan=$clickableSpan")  
    }  
}

效果:

20240604162019.png